package info.niwota.web.proxy;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Extends Java URI with setters
 *  
 * @author qiangli
 * 
 */
public class Uri {
	private static final Pattern PARAMS_PATTERN = Pattern.compile("(?:^|&)([^&=]*)=?([^&]*)");

	private URI uri;

	private Map<String, String> params = new TreeMap<String, String>();

	private boolean dirtyQuery;

	public Uri(String uri) throws URISyntaxException {
		this(new URI(uri));
	}
	
	private Uri(URI uri) {
		this.uri = uri;
		initQuery();
	}

	private void initQuery() {
		String query = uri.getQuery();

		if (query == null) {
			return;
		}

		Matcher m = PARAMS_PATTERN.matcher(query);
		while (m.find()) {
			params.put(decode(m.group(1)), decode(m.group(2)));
		}
	}

	private URI buildQuery() {
		dirtyQuery = false;
		StringBuilder sb = new StringBuilder();
		boolean ampsand = false;
		for (Entry<String, String> i : params.entrySet()) {
			if (ampsand) {
				sb.append("&");
			}
			sb.append(encode(i.getKey()));
			sb.append("=");
			sb.append(encode(i.getValue()));
			ampsand = true;
		}
		String query = sb.length() > 0 ? sb.toString() : null;
		try {
			uri = new URI(uri.getScheme(), uri.getAuthority(), uri.getPath(), query, uri.getFragment());
		} catch (URISyntaxException e) {
			e.printStackTrace();
		}
		return uri;
	}

	private String decode(String s) {
		if (s == null) {
			return null;
		}
		try {
			URLDecoder.decode(s, "UTF-8");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		return s;
	}

	private String encode(String s) {
		if (s == null) {
			return null;
		}
		try {
			URLEncoder.encode(s, "UTF-8");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		return s;
	}

	// parameters

	public String getParameter(String key) {
		return params.get(key);
	}

	public Uri setParameter(String key, String value) {
		this.dirtyQuery = true;
		params.put(key, value);
		return this;
	}

	public String removeParameter(String key) {
		this.dirtyQuery = true;
		return params.remove(key);
	}

	public Uri setParameters(Map<String, String> parameters) {
		this.dirtyQuery = true;
		params.clear();
		if (parameters != null) {
			params.putAll(parameters);
		}
		return this;
	}
	
	public Uri clearParameters() {
		this.dirtyQuery = true;
		params.clear();
		return this;
	}
	
	public Set<Entry<String, String>> getParameters() {
		return params.entrySet();
	}

	private URI getUri() {
		return dirtyQuery ? buildQuery() : uri;
	}

	// setter

	public Uri setScheme(String scheme) {
		try {
			uri = new URI(scheme, uri.getSchemeSpecificPart(), uri.getFragment());
		} catch (URISyntaxException e) {
			e.printStackTrace();
		}
		return this;
	}

	public Uri setSchemeSpecificPart(String ssp) {
		try {
			uri = new URI(uri.getScheme(), ssp, uri.getFragment());
		} catch (URISyntaxException e) {
			e.printStackTrace();
		}
		return this;
	}

	public Uri setAuthority(String authority) {
		try {
			uri = new URI(uri.getScheme(), authority, uri.getPath(), uri.getQuery(), uri.getFragment());
		} catch (URISyntaxException e) {
			e.printStackTrace();
		}
		return this;
	}

	public Uri setUserInfo(String userInfo) {
		try {
			uri = new URI(uri.getScheme(), userInfo, uri.getHost(), uri.getPort(), uri.getPath(), uri.getQuery(), uri.getFragment());
		} catch (URISyntaxException e) {
			e.printStackTrace();
		}
		return this;
	}

	public Uri setHost(String host) {
		try {
			uri = new URI(uri.getScheme(), uri.getUserInfo(), host, uri.getPort(), uri.getPath(), uri.getQuery(), uri.getFragment());
		} catch (URISyntaxException e) {
			e.printStackTrace();
		}
		return this;
	}

	public Uri setPort(int port) {
		try {
			uri = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), port, uri.getPath(), uri.getQuery(), uri.getFragment());
		} catch (URISyntaxException e) {
			e.printStackTrace();
		}
		return this;
	}

	public Uri setPath(String path) {
		try {
			uri = new URI(uri.getScheme(), uri.getAuthority(), path, uri.getQuery(), uri.getFragment());
		} catch (URISyntaxException e) {
			e.printStackTrace();
		}
		return this;
	}

	public Uri setQuery(String query) {
		try {
			uri = new URI(uri.getScheme(), uri.getAuthority(), uri.getPath(), query, uri.getFragment());
		} catch (URISyntaxException e) {
			e.printStackTrace();
		}
		return this;
	}

	public Uri setFragment(String fragment) {
		try {
			uri = new URI(uri.getScheme(), uri.getSchemeSpecificPart(), fragment);
		} catch (URISyntaxException e) {
			e.printStackTrace();
		}
		return this;
	}

	//

	public String getFile() {
		String p = uri.getPath();
		String q = uri.getQuery();
		return q == null ? p : p + "?" + q;
	}

	public byte[] getContent() throws IOException {
		InputStream is = uri.toURL().openStream();
		ByteArrayOutputStream os = new ByteArrayOutputStream();
		try {
			byte[] buf = new byte[1024];
			while (true) {
				int read = is.read(buf);
				if (read == -1) {
					break;
				}
				if (read > 0) {
					os.write(buf, 0, read);
				}
			}
		} finally {
			is.close();
			os.close();
		}
		return os.toByteArray();
	}

	// delegate to URI
	public URI parseServerAuthority() throws URISyntaxException {
		return getUri().parseServerAuthority();
	}

	public Uri normalize() {
		return new Uri(getUri().normalize());
	}

	public Uri resolve(URI uri) {
		return new Uri(getUri().resolve(uri));
	}

	public Uri resolve(String str) {
		return new Uri(getUri().resolve(str));
	}

	public Uri relativize(URI uri) {
		return new Uri(getUri().relativize(uri));
	}

	public URL toURL() throws MalformedURLException {
		return getUri().toURL();
	}

	public String getScheme() {
		return getUri().getScheme();
	}

	public boolean isAbsolute() {
		return getUri().isAbsolute();
	}

	public boolean isOpaque() {
		return getUri().isOpaque();
	}

	public String getRawSchemeSpecificPart() {
		return getUri().getRawSchemeSpecificPart();
	}

	public String getSchemeSpecificPart() {
		return getUri().getSchemeSpecificPart();
	}

	public String getRawAuthority() {
		return getUri().getRawAuthority();
	}

	public String getAuthority() {
		return getUri().getAuthority();
	}

	public String getRawUserInfo() {
		return getUri().getRawUserInfo();
	}

	public String getUserInfo() {
		return getUri().getUserInfo();
	}

	public String getHost() {
		return getUri().getHost();
	}

	public int getPort() {
		return getUri().getPort();
	}

	public String getRawPath() {
		return getUri().getRawPath();
	}

	public String getPath() {
		return getUri().getPath();
	}

	public String getRawQuery() {
		return getUri().getRawQuery();
	}

	public String getQuery() {
		return getUri().getQuery();
	}

	public String getRawFragment() {
		return getUri().getRawFragment();
	}

	public String getFragment() {
		return getUri().getFragment();
	}

	public boolean equals(Object ob) {
		return getUri().equals(ob);
	}

	public int hashCode() {
		return getUri().hashCode();
	}

	public int compareTo(Uri that) {
		return getUri().compareTo(that.getUri());
	}

	public String toString() {
		return getUri().toString();
	}

	public String toASCIIString() {
		return getUri().toASCIIString();
	}
}
